﻿using System;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Security.Permissions;
using System.Text;
using System.Threading;
using System.Web.Security;
using Microsoft.WindowsAzure;
using Microsoft.WindowsAzure.StorageClient;
using RRSimulator.BlobManager.Service.Requests;
using RRSimulator.BlobManager.Service.Responses;

namespace RRSimulator.BlobManager.Service
{
    public class BlobManager : IBlobManager
    {
        private const string HASH_METADATA_NAME = "HashValue";

        //[PrincipalPermission(SecurityAction.Demand, Role = "Write")]
        public UploadFileResponse UploadFile(UploadFileRequest request)
        {
            Stopwatch stopwatch = new Stopwatch();
            stopwatch.Start();

            UploadFileResponse response = null;

            if (request != null)
            {
                response = new UploadFileResponse() { BlobName = request.BlobName };

                if (ValidateUserPermission(request, response, "Write"))
                
                if (ValidateUploadFileRequest(request, response))
                {
                    try
                    {
                        CloudStorageAccount account = CloudStorageAccount.DevelopmentStorageAccount;
                        CloudBlobClient blobClient = account.CreateCloudBlobClient();
                        CloudBlobContainer container = blobClient.GetContainerReference(request.ContainerName);
                        container.CreateIfNotExist();
                        CloudBlob blob = container.GetBlobReference(request.BlobName);

                        blob.UploadFromStream(new MemoryStream(request.FileBytes, true));

                        // set the required content type 
                        blob.Properties.ContentType = request.ContentType;
                        blob.SetProperties();
                        blob.Metadata.Add(HASH_METADATA_NAME, request.HashValue);
                        blob.SetMetadata();
                    }
                    catch (Exception ex)
                    {
                        response.ErrorMessage = string.Format("An exception occurred : {0}", ex.Message);
                    }
                }
            }

            stopwatch.Stop();
            response.ElapsedTime = string.Format("{0}:{1}", Math.Floor(stopwatch.Elapsed.TotalMinutes), stopwatch.Elapsed.ToString("ss\\.ff"));

            return response;
        }

        [PrincipalPermission(SecurityAction.Demand, Role = "Write")]
        public DeleteFileResponse DeleteFile(DeleteFileRequest request)
        {
            Stopwatch stopwatch = new Stopwatch();
            stopwatch.Start();

            DeleteFileResponse response = null;

            if (request != null)
            {
                response = new DeleteFileResponse() { BlobName = request.BlobName };

                if (ValidateRequest(request, response))
                {
                    try
                    {
                        CloudStorageAccount account = CloudStorageAccount.DevelopmentStorageAccount;
                        CloudBlobClient blobClient = account.CreateCloudBlobClient();
                        CloudBlobContainer container = blobClient.GetContainerReference(request.ContainerName);
                        CloudBlob blob = container.GetBlobReference(request.BlobName);

                        blob.DeleteIfExists();
                    }
                    catch (Exception ex)
                    {
                        response.ErrorMessage = string.Format("An exception occurred : {0}", ex.Message);
                    }
                }
            }

            stopwatch.Stop();
            response.ElapsedTime = string.Format("{0}:{1}", Math.Floor(stopwatch.Elapsed.TotalMinutes), stopwatch.Elapsed.ToString("ss\\.ff"));

            return response;
        }

        [PrincipalPermission(SecurityAction.Demand, Role = "Write")]
        public UploadBlockResponse UploadBlock(UploadBlockRequest request)
        {
            Stopwatch stopwatch = new Stopwatch();
            stopwatch.Start();

            UploadBlockResponse response = null;

            if (request != null)
            {
                response = new UploadBlockResponse() { BlobName = request.BlobName, BlockId = request.BlockId };

                if (ValidateUploadBlockRequest(request, response))
                {
                    try
                    {
                        CloudStorageAccount account = CloudStorageAccount.DevelopmentStorageAccount;
                        CloudBlobClient blobClient = account.CreateCloudBlobClient();
                        CloudBlobContainer container = blobClient.GetContainerReference(request.ContainerName);
                        container.CreateIfNotExist();
                        CloudBlockBlob blockBlob = container.GetBlockBlobReference(request.BlobName);

                        blockBlob.PutBlock(request.BlockId, new MemoryStream(request.BlockBytes, true), null);
                    }
                    catch (Exception ex)
                    {
                        response.ErrorMessage = string.Format("An exception occurred : {0}", ex.Message);
                    }
                }
            }

            stopwatch.Stop();
            response.ElapsedTime = string.Format("{0}:{1}", Math.Floor(stopwatch.Elapsed.TotalMinutes), stopwatch.Elapsed.ToString("ss\\.ff"));

            return response;
        }

        [PrincipalPermission(SecurityAction.Demand, Role = "Write")]
        public CommitBlocksResponse CommitBlocks(CommitBlocksRequest request)
        {
            Stopwatch stopwatch = new Stopwatch();
            stopwatch.Start();

            CommitBlocksResponse response = null;

            if (request != null)
            {
                response = new CommitBlocksResponse() { BlobName = request.BlobName };

                if (ValidateCommitBlocksRequest(request, response))
                {
                    try
                    {
                        CloudStorageAccount account = CloudStorageAccount.DevelopmentStorageAccount;
                        CloudBlobClient blobClient = account.CreateCloudBlobClient();
                        CloudBlobContainer container = blobClient.GetContainerReference(request.ContainerName);
                        CloudBlockBlob blockBlob = container.GetBlockBlobReference(request.BlobName);
                        var blockList = blockBlob.DownloadBlockList();
                        var matchedBlockList = blockList.Where(b => request.BlockIds.FirstOrDefault(i => i.Equals(b.Name, StringComparison.CurrentCultureIgnoreCase)) != null);
                        blockBlob.PutBlockList(request.BlockIds);

                        blockBlob.Properties.ContentType = request.ContentType;
                        blockBlob.SetProperties();
                        blockBlob.Metadata.Add(HASH_METADATA_NAME, request.HashValue);
                        blockBlob.SetMetadata();
                    }
                    catch (Exception ex)
                    {
                        response.ErrorMessage = string.Format("An exception occurred : {0}", ex.Message);
                    }
                }
            }

            stopwatch.Stop();
            response.ElapsedTime = string.Format("{0}:{1}", Math.Floor(stopwatch.Elapsed.TotalMinutes), stopwatch.Elapsed.ToString("ss\\.ff"));

            return response;
        }

        //[PrincipalPermission(SecurityAction.Demand, Role = "Read")]
        public TestOperationResponse TestOperation(TestOperationRequest request)
        {
            Stopwatch stopwatch = new Stopwatch();
            stopwatch.Start();

            TestOperationResponse response = new TestOperationResponse { TestString = request.TestString };

            ValidateUserPermission(request, response, "Read");

            stopwatch.Stop();
            response.ElapsedTime = string.Format("{0}:{1}", Math.Floor(stopwatch.Elapsed.TotalMinutes), stopwatch.Elapsed.ToString("ss\\.ff"));

            return response;
        }

        [PrincipalPermission(SecurityAction.Demand, Role = "Write")]
        public UploadFileStreamResponse UploadFileStream(UploadFileStreamRequest request)
        {
            Stopwatch stopwatch = new Stopwatch();
            stopwatch.Start();

            UploadFileStreamResponse response = null;

            if (request != null)
            {
                response = new UploadFileStreamResponse() { BlobName = request.BlobName };

                if (ValidateUploadFileStreamRequest(request, response))
                {
                    try
                    {
                        CloudStorageAccount account = CloudStorageAccount.DevelopmentStorageAccount;
                        CloudBlobClient blobClient = account.CreateCloudBlobClient();
                        CloudBlobContainer container = blobClient.GetContainerReference(request.ContainerName);
                        container.CreateIfNotExist();
                        CloudBlob blob = container.GetBlobReference(request.BlobName);

                        blob.UploadFromStream(request.FileStream);

                        // set the required content type 
                        blob.Properties.ContentType = request.ContentType;
                        blob.SetProperties();
                        blob.Metadata.Add(HASH_METADATA_NAME, request.HashValue);
                        blob.SetMetadata();
                    }
                    catch (Exception ex)
                    {
                        response.ErrorMessage = string.Format("An exception occurred : {0}", ex.Message);
                    }
                }
            }

            stopwatch.Stop();
            response.ElapsedTime = string.Format("{0}:{1}", Math.Floor(stopwatch.Elapsed.TotalMinutes), stopwatch.Elapsed.ToString("ss\\.ff"));

            return response;
        }

        [PrincipalPermission(SecurityAction.Demand, Role = "Write")]
        public UploadBlockStreamResponse UploadBlockStream(UploadBlockStreamRequest request)
        {
            Stopwatch stopwatch = new Stopwatch();
            stopwatch.Start();

            UploadBlockStreamResponse response = null;

            if (request != null)
            {
                response = new UploadBlockStreamResponse() { BlobName = request.BlobName, BlockId = request.BlockId };

                if (ValidateUploadBlockStreamRequest(request, response))
                {
                    try
                    {
                        CloudStorageAccount account = CloudStorageAccount.DevelopmentStorageAccount;
                        CloudBlobClient blobClient = account.CreateCloudBlobClient();
                        CloudBlobContainer container = blobClient.GetContainerReference(request.ContainerName);
                        container.CreateIfNotExist();
                        CloudBlockBlob blockBlob = container.GetBlockBlobReference(request.BlobName);

                        using (MemoryStream buffer = new MemoryStream())
                        {
                            request.BlockStream.CopyTo(buffer);
                            buffer.Position = 0;

                            blockBlob.PutBlock(request.BlockId, buffer, null);

                            buffer.Close();
                            buffer.Dispose();
                        }
                    }
                    catch (Exception ex)
                    {
                        response.ErrorMessage = string.Format("An exception occurred : {0}", ex.Message);
                    }
                }
            }

            stopwatch.Stop();
            response.ElapsedTime = string.Format("{0}:{1}", Math.Floor(stopwatch.Elapsed.TotalMinutes), stopwatch.Elapsed.ToString("ss\\.ff"));

            return response;
        }

        private bool ValidateRequest(IBlobManagerRequest request, IBlobManagerResponse response)
        {
            StringBuilder errorMessages = new StringBuilder(response.ErrorMessage);

            if (string.IsNullOrEmpty(request.ContainerName))
            {
                errorMessages.AppendLine("Invalid Container Name");
            }

            if (string.IsNullOrEmpty(request.BlobName))
            {
                errorMessages.AppendLine("Invalid Blob Name");
            }

            response.ErrorMessage = errorMessages.ToString();

            return !response.IsError;
        }

        private bool ValidateUploadFileRequest(UploadFileRequest request, UploadFileResponse response)
        {
            ValidateRequest(request, response);

            StringBuilder errorMessages = new StringBuilder(response.ErrorMessage);

            if (string.IsNullOrEmpty(request.HashValue))
            {
                errorMessages.AppendLine("Invalid Hash Value");
            }

            if (request.FileBytes == null)
            {
                errorMessages.AppendLine("Null File Stream");
            }

            response.ErrorMessage = errorMessages.ToString();

            return !response.IsError;
        }

        private bool ValidateUploadFileStreamRequest(UploadFileStreamRequest request, UploadFileStreamResponse response)
        {
            ValidateRequest(request, response);

            StringBuilder errorMessages = new StringBuilder(response.ErrorMessage);

            if (string.IsNullOrEmpty(request.HashValue))
            {
                errorMessages.AppendLine("Invalid Hash Value");
            }

            if (request.FileStream == null)
            {
                errorMessages.AppendLine("Null File Stream");
            }

            response.ErrorMessage = errorMessages.ToString();

            return !response.IsError;
        }

        private bool ValidateUploadBlockRequest(UploadBlockRequest request, UploadBlockResponse response)
        {
            ValidateRequest(request, response);

            StringBuilder errorMessages = new StringBuilder(response.ErrorMessage);

            if (string.IsNullOrEmpty(request.BlockId))
            {
                errorMessages.AppendLine("Invalid BlockId");
            }

            if (request.BlockBytes == null)
            {
                errorMessages.AppendLine("Null Block Stream");
            }

            response.ErrorMessage = errorMessages.ToString();

            return !response.IsError;
        }

        private bool ValidateUploadBlockStreamRequest(UploadBlockStreamRequest request, UploadBlockStreamResponse response)
        {
            ValidateRequest(request, response);

            StringBuilder errorMessages = new StringBuilder(response.ErrorMessage);

            if (string.IsNullOrEmpty(request.BlockId))
            {
                errorMessages.AppendLine("Invalid BlockId");
            }

            if (request.BlockStream == null)
            {
                errorMessages.AppendLine("Null Block Stream");
            }

            response.ErrorMessage = errorMessages.ToString();

            return !response.IsError;
        }

        private bool ValidateCommitBlocksRequest(CommitBlocksRequest request, CommitBlocksResponse response)
        {
            ValidateRequest(request, response);

            StringBuilder errorMessages = new StringBuilder(response.ErrorMessage);

            if (string.IsNullOrEmpty(request.HashValue))
            {
                errorMessages.AppendLine("Invalid Hash Value");
            }

            if (request.BlockIds == Enumerable.Empty<string>())
            {
                errorMessages.AppendLine("Empty BlockId list");
            }

            response.ErrorMessage = errorMessages.ToString();

            return !response.IsError;
        }

        private bool ValidateUserPermission(IBlobManagerRequest request, IBlobManagerResponse response, string role)
        {
            var userPrincipal = System.Web.HttpContext.Current.User;
            var isUserPrincipalSameWithThread = userPrincipal == Thread.CurrentPrincipal;

            if (!userPrincipal.IsInRole(role))
            {
                response.ErrorMessage = "Access Denied.";
            }

            return !response.IsError;
        }
    }

    public static class BlobExtensions
    {
        public static bool Exists(this CloudBlob blob)
        {
            try
            {
                blob.FetchAttributes();
                return true;
            }
            catch (StorageClientException e)
            {
                if (e.ErrorCode == StorageErrorCode.ResourceNotFound)
                {
                    return false;
                }
                else
                {
                    throw;
                }
            }
        }
    }

    public static class ContainerExtensions
    {
        public static bool Exists(this CloudBlobContainer container)
        {
            try
            {
                container.FetchAttributes();
                return true;
            }
            catch (StorageClientException e)
            {
                if (e.ErrorCode == StorageErrorCode.ResourceNotFound)
                {
                    return false;
                }
                else
                {
                    throw;
                }
            }
        }
    }
}
